package com.googlecode.YaccSyntaxTool.grammar;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import com.googlecode.YaccSyntaxTool.export.Exporter;
import com.googlecode.YaccSyntaxTool.export.NotSupportedExportKindException;
import com.googlecode.YaccSyntaxTool.export.ReferenceMap;

/**
 * This class represents one term (= one line) in the y.output file.
 *
 * @author goe
 *
 */
public class Term {
	private final static String EMPTY_TERM = "/* empty */";

	private final List<Factor> factors;

	private boolean empty = false;	// empty terms are used in BNF to implement optional nonterminals

	public Term() {
		super();

		factors = new ArrayList<Factor>();
	}

	/*
	 * PARSE from file
	 */
	public Term(String iTermStr, Set<String> excludes) {
		this();

		// remove comments
		iTermStr = iTermStr.replaceAll("/\\*(?:.|[\\n\\r])*?\\*/","");

		if (iTermStr.isEmpty()) {
			// this is a empty term
			this.empty = true;
		} else {
			// split term into factors
			for (String factorStr : iTermStr.split(" ")) {
				factors.add(new Factor(factorStr,excludes));
			}
		}
	}

	public boolean isEmpty() {
		return this.empty;
	}

	public String toString() {
		StringBuffer buf = new StringBuffer();
		boolean first = true;

		for (Factor factor : factors) {
			if (!first) {
				buf.append(" ");
			}
			buf.append(factor);
			first = false;
		}
		return buf.toString();
	}

	public void addFactor(Factor factor) {
		factors.add(factor);
	}

	public void removeFactor(Factor factor) {
		factors.remove(factor);
	}

	public Factor getFactor(int i) {
		if (i<factors.size()) {
			return factors.get(i);
		} else {
			return null;
		}
	}

	public List<Factor> getFactors() {
		return factors;
	}

	public int getFactorCnt() {
		return factors.size();
	}

	public boolean equals(Term iTerm) {
		return factors.equals(iTerm.getFactors());
	}

	/*
	 * EXPORT
	 */
	public void export(Exporter exporter) throws NotSupportedExportKindException {
		boolean first = true;

		switch (exporter.getKind()) {
		case HTMLCOMPACT:
			if (isEmpty()) {
				exporter.appendColored(EMPTY_TERM,"lightblue");
			}
			// fall through
		case TXT:
		case HTML:
			for (Factor factor : factors) {
				if (!first) {
					exporter.append(" ");
				}
				factor.export(exporter);
				first = false;
			}
			break;
		default:
			throw new NotSupportedExportKindException(exporter.getKind());
		}
	}

	/*
	 * REFERENCES
	 */
	public void fillReferenceMap(ReferenceMap refMap, FactorType type, String refFrom) {

		for (Factor factor : factors) {
			factor.fillReferenceMap(refMap,type,refFrom);
		}
	}

	/*
	 * SIMPLIFY
	 */
	public int getSimilarity(Term iTerm) {
		int rv = 0, idx=0;

		for (Factor factor : factors) {
			Factor otherFactor = iTerm.getFactor(idx);
			if (otherFactor != null && factor.equals(otherFactor)) {
				idx++;
				rv++;
			}
		}
		return rv;
	}

	public void makeOptionalExpression(Term iTerm) {
		int idx=0;

		Expression	optionalExpression = new Expression();
		Term		optionalTerm = new Term();
		optionalExpression.addTerm(optionalTerm);

		List<Factor> obsoleteFactors = new ArrayList<Factor>();

		boolean first = true;

		for (int i=0; i<factors.size(); i++) {

			Factor factor = factors.get(i);
			Factor otherFactor = iTerm.getFactor(idx);

			if (otherFactor != null && factor.equals(otherFactor)) {
				idx++;
			} else {
				// this is a optional factor!
				optionalTerm.addFactor(factor);
				if (!first) {
					// just remove all following factors
					obsoleteFactors.add(factor);
				} else {
					// replace the first optional factor with the new optional expression
					factors.set(i, new Factor(FactorType.OPTION,optionalExpression));
				}
				first = false;
			}
		}

		// delete all obsolete factors
		factors.removeAll(obsoleteFactors);
	}

	/*
	 * EXTRACT
	 */
	public void fillUsedNonterminals(Set<String> usedNonterminals) {
		for (Factor factor : factors) {
			factor.fillSet(usedNonterminals,FactorType.NONTERMINAL);
		}
	}
}
